package com.hww.common.util;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

public final class FileIOUtils {

	private static int sBufferSize = 8192;

	private FileIOUtils() {
		throw new UnsupportedOperationException("u can't instantiate me...");
	}

	/**
	 * Write file from input stream.
	 *
	 * @param filePath The path of file.
	 * @param is       The input stream.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromIS(final String filePath, final InputStream is) {
		return writeFileFromIS(getFileByPath(filePath), is, false);
	}

	/**
	 * Write file from input stream.
	 *
	 * @param filePath The path of file.
	 * @param is       The input stream.
	 * @param append   True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromIS(final String filePath, final InputStream is, final boolean append) {
		return writeFileFromIS(getFileByPath(filePath), is, append);
	}

	/**
	 * Write file from input stream.
	 *
	 * @param file The file.
	 * @param is   The input stream.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromIS(final File file, final InputStream is) {
		return writeFileFromIS(file, is, false);
	}

	/**
	 * Write file from input stream.
	 *
	 * @param file   The file.
	 * @param is     The input stream.
	 * @param append True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromIS(final File file, final InputStream is, final boolean append) {
		if (!createOrExistsFile(file) || is == null)
			return false;
		OutputStream os = null;
		try {
			os = new BufferedOutputStream(new FileOutputStream(file, append));
			byte data[] = new byte[sBufferSize];
			int len;
			while ((len = is.read(data, 0, sBufferSize)) != -1) {
				os.write(data, 0, len);
			}
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Write file from bytes by stream.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes) {
		return writeFileFromBytesByStream(getFileByPath(filePath), bytes, false);
	}

	/**
	 * Write file from bytes by stream.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @param append   True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes, final boolean append) {
		return writeFileFromBytesByStream(getFileByPath(filePath), bytes, append);
	}

	/**
	 * Write file from bytes by stream.
	 *
	 * @param file  The file.
	 * @param bytes The bytes.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes) {
		return writeFileFromBytesByStream(file, bytes, false);
	}

	/**
	 * Write file from bytes by stream.
	 *
	 * @param file   The file.
	 * @param bytes  The bytes.
	 * @param append True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes, final boolean append) {
		if (bytes == null || !createOrExistsFile(file))
			return false;
		BufferedOutputStream bos = null;
		try {
			bos = new BufferedOutputStream(new FileOutputStream(file, append));
			bos.write(bytes);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				if (bos != null) {
					bos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Write file from bytes by channel.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @param isForce  是否写入文件
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByChannel(final String filePath, final byte[] bytes,
			final boolean isForce) {
		return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, false, isForce);
	}

	/**
	 * Write file from bytes by channel.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @param append   True to append, false otherwise.
	 * @param isForce  True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByChannel(final String filePath, final byte[] bytes, final boolean append,
			final boolean isForce) {
		return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, append, isForce);
	}

	/**
	 * Write file from bytes by channel.
	 *
	 * @param file    The file.
	 * @param bytes   The bytes.
	 * @param isForce True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByChannel(final File file, final byte[] bytes, final boolean isForce) {
		return writeFileFromBytesByChannel(file, bytes, false, isForce);
	}

	/**
	 * Write file from bytes by channel.
	 *
	 * @param file    The file.
	 * @param bytes   The bytes.
	 * @param append  True to append, false otherwise.
	 * @param isForce True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByChannel(final File file, final byte[] bytes, final boolean append,
			final boolean isForce) {
		if (bytes == null)
			return false;
		FileChannel fc = null;
		try {
			fc = new FileOutputStream(file, append).getChannel();
			fc.position(fc.size());
			fc.write(ByteBuffer.wrap(bytes));
			if (isForce)
				fc.force(true);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				if (fc != null) {
					fc.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Write file from bytes by map.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @param isForce  True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByMap(final String filePath, final byte[] bytes, final boolean isForce) {
		return writeFileFromBytesByMap(filePath, bytes, false, isForce);
	}

	/**
	 * Write file from bytes by map.
	 *
	 * @param filePath The path of file.
	 * @param bytes    The bytes.
	 * @param append   True to append, false otherwise.
	 * @param isForce  True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByMap(final String filePath, final byte[] bytes, final boolean append,
			final boolean isForce) {
		return writeFileFromBytesByMap(getFileByPath(filePath), bytes, append, isForce);
	}

	/**
	 * Write file from bytes by map.
	 *
	 * @param file    The file.
	 * @param bytes   The bytes.
	 * @param isForce True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByMap(final File file, final byte[] bytes, final boolean isForce) {
		return writeFileFromBytesByMap(file, bytes, false, isForce);
	}

	/**
	 * Write file from bytes by map.
	 *
	 * @param file    The file.
	 * @param bytes   The bytes.
	 * @param append  True to append, false otherwise.
	 * @param isForce True to force write file, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromBytesByMap(final File file, final byte[] bytes, final boolean append,
			final boolean isForce) {
		if (bytes == null || !createOrExistsFile(file))
			return false;
		FileChannel fc = null;
		try {
			fc = new FileOutputStream(file, append).getChannel();
			MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length);
			mbb.put(bytes);
			if (isForce)
				mbb.force();
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				if (fc != null) {
					fc.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Write file from string.
	 *
	 * @param filePath The path of file.
	 * @param content  The string of content.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromString(final String filePath, final String content) {
		return writeFileFromString(getFileByPath(filePath), content, false);
	}

	/**
	 * Write file from string.
	 *
	 * @param filePath The path of file.
	 * @param content  The string of content.
	 * @param append   True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromString(final String filePath, final String content, final boolean append) {
		return writeFileFromString(getFileByPath(filePath), content, append);
	}

	/**
	 * Write file from string.
	 *
	 * @param file    The file.
	 * @param content The string of content.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromString(final File file, final String content) {
		return writeFileFromString(file, content, false);
	}

	/**
	 * Write file from string.
	 *
	 * @param file    The file.
	 * @param content The string of content.
	 * @param append  True to append, false otherwise.
	 * @return {@code true}: success<br>
	 *         {@code false}: fail
	 */
	public static boolean writeFileFromString(final File file, final String content, final boolean append) {
		if (file == null || content == null)
			return false;
		if (!createOrExistsFile(file))
			return false;
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(file, append));
			bw.write(content);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				if (bw != null) {
					bw.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// the divide line of write and read
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Return the lines in file.
	 *
	 * @param filePath The path of file.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final String filePath) {
		return readFile2List(getFileByPath(filePath), null);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param filePath    The path of file.
	 * @param charsetName The name of charset.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final String filePath, final String charsetName) {
		return readFile2List(getFileByPath(filePath), charsetName);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param file The file.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final File file) {
		return readFile2List(file, 0, 0x7FFFFFFF, null);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param file        The file.
	 * @param charsetName The name of charset.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final File file, final String charsetName) {
		return readFile2List(file, 0, 0x7FFFFFFF, charsetName);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param filePath The path of file.
	 * @param st       The line's index of start.
	 * @param end      The line's index of end.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final String filePath, final int st, final int end) {
		return readFile2List(getFileByPath(filePath), st, end, null);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param filePath    The path of file.
	 * @param st          The line's index of start.
	 * @param end         The line's index of end.
	 * @param charsetName The name of charset.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final String filePath, final int st, final int end,
			final String charsetName) {
		return readFile2List(getFileByPath(filePath), st, end, charsetName);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param file The file.
	 * @param st   The line's index of start.
	 * @param end  The line's index of end.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final File file, final int st, final int end) {
		return readFile2List(file, st, end, null);
	}

	/**
	 * Return the lines in file.
	 *
	 * @param file        The file.
	 * @param st          The line's index of start.
	 * @param end         The line's index of end.
	 * @param charsetName The name of charset.
	 * @return the lines in file
	 */
	public static List<String> readFile2List(final File file, final int st, final int end, final String charsetName) {
		if (!isFileExists(file))
			return null;
		if (st > end)
			return null;
		BufferedReader reader = null;
		try {
			String line;
			int curLine = 1;
			List<String> list = new ArrayList<>();
			if (isSpace(charsetName)) {
				reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
			} else {
				reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
			}
			while ((line = reader.readLine()) != null) {
				if (curLine > end)
					break;
				if (st <= curLine && curLine <= end)
					list.add(line);
				++curLine;
			}
			return list;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Return the string in file.
	 *
	 * @param filePath The path of file.
	 * @return the string in file
	 */
	public static String readFile2String(final String filePath) {
		return readFile2String(getFileByPath(filePath), null);
	}

	/**
	 * Return the string in file.
	 *
	 * @param filePath    The path of file.
	 * @param charsetName The name of charset.
	 * @return the string in file
	 */
	public static String readFile2String(final String filePath, final String charsetName) {
		return readFile2String(getFileByPath(filePath), charsetName);
	}

	/**
	 * Return the string in file.
	 *
	 * @param file The file.
	 * @return the string in file
	 */
	public static String readFile2String(final File file) {
		return readFile2String(file, null);
	}

	/**
	 * Return the string in file.
	 *
	 * @param file        The file.
	 * @param charsetName The name of charset.
	 * @return the string in file
	 */
	public static String readFile2String(final File file, final String charsetName) {
		byte[] bytes = readFile2BytesByStream(file);
		if (bytes == null)
			return null;
		if (isSpace(charsetName)) {
			return new String(bytes);
		} else {
			try {
				return new String(bytes, charsetName);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
				return "";
			}
		}
	}

	/**
	 * Return the bytes in file by stream.
	 *
	 * @param filePath The path of file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByStream(final String filePath) {
		return readFile2BytesByStream(getFileByPath(filePath));
	}

	/**
	 * Return the bytes in file by stream.
	 *
	 * @param file The file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByStream(final File file) {
		if (!isFileExists(file))
			return null;
		try {
			return is2Bytes(new FileInputStream(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Return the bytes in file by channel.
	 *
	 * @param filePath The path of file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByChannel(final String filePath) {
		return readFile2BytesByChannel(getFileByPath(filePath));
	}

	/**
	 * Return the bytes in file by channel.
	 *
	 * @param file The file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByChannel(final File file) {
		if (!isFileExists(file))
			return null;
		FileChannel fc = null;
		try {
			fc = new RandomAccessFile(file, "r").getChannel();
			ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size());
			while (true) {
				if (!((fc.read(byteBuffer)) > 0))
					break;
			}
			return byteBuffer.array();
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			try {
				if (fc != null) {
					fc.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Return the bytes in file by map.
	 *
	 * @param filePath The path of file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByMap(final String filePath) {
		return readFile2BytesByMap(getFileByPath(filePath));
	}

	/**
	 * Return the bytes in file by map.
	 *
	 * @param file The file.
	 * @return the bytes in file
	 */
	public static byte[] readFile2BytesByMap(final File file) {
		if (!isFileExists(file))
			return null;
		FileChannel fc = null;
		try {
			fc = new RandomAccessFile(file, "r").getChannel();
			int size = (int) fc.size();
			MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
			byte[] result = new byte[size];
			mbb.get(result, 0, size);
			return result;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			try {
				if (fc != null) {
					fc.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Set the buffer's size.
	 * <p>
	 * Default size equals 8192 bytes.
	 * </p>
	 *
	 * @param bufferSize The buffer's size.
	 */
	public static void setBufferSize(final int bufferSize) {
		sBufferSize = bufferSize;
	}

	public static File getFileByPath(final String filePath) {
		return isSpace(filePath) ? null : new File(filePath);
	}

	public static boolean createOrExistsFile(final String filePath) {
		return createOrExistsFile(getFileByPath(filePath));
	}

	private static boolean createOrExistsFile(final File file) {
		if (file == null)
			return false;
		if (file.exists())
			return file.isFile();
		if (!createOrExistsDir(file.getParentFile()))
			return false;
		try {
			return file.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

	private static boolean createOrExistsDir(final File file) {
		return file != null && ((file.exists() && file.isDirectory()) ? (file.isDirectory()) : file.mkdirs());
	}

	public static boolean createOrExistsDir(final String filePath) {
		return createOrExistsDir(getFileByPath(filePath));
	}

	private static boolean isFileExists(final File file) {
		return file != null && file.exists();
	}

	public static boolean isSpace(final String s) {
		if (s == null)
			return true;
		for (int i = 0, len = s.length(); i < len; ++i) {
			if (!Character.isWhitespace(s.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	private static byte[] is2Bytes(final InputStream is) {
		if (is == null)
			return null;
		ByteArrayOutputStream os = null;
		try {
			os = new ByteArrayOutputStream();
			byte[] b = new byte[sBufferSize];
			int len;
			while ((len = is.read(b, 0, sBufferSize)) != -1) {
				os.write(b, 0, len);
			}
			return os.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 保存对象到文件
	 *
	 * @param object
	 * @param path
	 */
	public static void writeObject(Object object, String path) {
		File file = new File(path);
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream(file));
			oos.writeObject(object);
			oos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
					oos = null;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 读取对象到内存
	 *
	 * @param path
	 * @return
	 */
	public static Object readObject(String path) {
		File file = new File(path);
		if (!file.exists()) {
			return null;
		}
		ObjectInputStream ois = null;
		Object object = null;
		try {
			ois = new ObjectInputStream(new FileInputStream(file));
			object = ois.readObject();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
					ois = null;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			return object;
		}
	}
}
